﻿//
//  Monkey Manager
//  Copyright (C) 2012 Victor Tereschenko
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//     
// ========================================================================

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Threading;

namespace ManagerDB
{
    /// <summary>
    /// Stores hashes of files.
    /// </summary>
    class FileHashes : IDisposable
    {
        public class FileDescription
        {
            public FileID ID;
            public string Path;
            public double WatchedCount;
        }

        string mPath;
        Thread mThread;
        List<FileDescription> mFiles = new List<FileDescription>();
        public FileHashes(string folder)
        {
            mPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "hashes.xml");
            if (File.Exists(mPath))
            {
                XmlDocument xml = new XmlDocument();
                xml.Load(mPath);
                var nodes = xml.SelectNodes("files//file");
                foreach (XmlNode node in nodes)
                {
                    var fileInfo = parse(node);
                    if (fileInfo != null)
                        mFiles.Add(fileInfo);
                }
            }
            mThread = new Thread(new ParameterizedThreadStart(backgroundHasher));
            mThread.Start(folder);
        }

        void backgroundHasher(object path)
        {
            scanFolder((string)path);
        }

        void scanFolder(string path)
        {
            string[] dirs = Directory.GetDirectories(path);
            foreach (string dir in dirs)
            {
                scanFolder(dir);
            }
            string[] files = Directory.GetFiles(path);
            foreach (string file in files)
            {
                FileDescription item;
                lock (mFiles)
                    item = mFiles.Find(f => f.Path == file);
                var fileInfo = new FileInfo(file);
                if (item != null)
                {
                    if (fileInfo.Length != item.ID.Size)
                    {
                        lock (mFiles)
                            mFiles.Remove(item);
                        item = createFile(file, fileInfo);
                        lock (mFiles)
                            mFiles.Add(item);
                    }
                }
                else 
                {
                    item = createFile(file, fileInfo);
                    lock (mFiles)
                        mFiles.Add(item);
                }
            }
        }

        private static FileDescription createFile(string file, FileInfo fileInfo)
        {
            FileDescription item = new FileDescription();
            item.Path = fileInfo.FullName;
            item.ID = new FileID();
            item.ID.Size = fileInfo.Length;
            item.ID.TTH = MonkeyManager.Hasher.GetTTH(file);
            return item;
        }
        
        /// <summary>
        /// Gets path by tth and size
        /// </summary>
        /// <param name="tth"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        public FileDescription GetPath(string tth, long size)
        {
            lock (mFiles)
                return mFiles.Find(f => f.ID.TTH == tth && f.ID.Size == size);
        }

        void save()
        {
            XmlDocument xml = new XmlDocument();
            var filesNode = xml.CreateElement("files");
            foreach (FileDescription file in mFiles)
            {
                var fileNode = xml.CreateElement("file");

                var pathAttr = xml.CreateAttribute("path");
                pathAttr.InnerText = file.Path;
                fileNode.Attributes.Append(pathAttr);

                var tthAttr = xml.CreateAttribute("TTH");
                tthAttr.InnerText = file.ID.TTH;
                fileNode.Attributes.Append(tthAttr);

                var sizeAttr = xml.CreateAttribute("size");
                sizeAttr.InnerText = file.ID.Size.ToString();
                fileNode.Attributes.Append(sizeAttr);

                var watchedAttr = xml.CreateAttribute("watchedCount");
                watchedAttr.InnerText = file.WatchedCount.ToString();
                fileNode.Attributes.Append(watchedAttr);

                filesNode.AppendChild(fileNode);
            }
            xml.AppendChild(filesNode);
            xml.Save(mPath);
        }

        FileDescription parse(XmlNode node)
        {
            if (node.Attributes["path"] == null)
                return null;
            var path = node.Attributes["path"].Value;
            long size;
            if (node.Attributes["size"] == null || !long.TryParse(node.Attributes["size"].Value, out size))
                return null;
            var fileInfo = new FileInfo(path);
            if (node.Attributes["TTH"] == null)
                return null;
            if (node.Attributes["watchedCount"] == null)
                return null;

            var desc = new FileDescription();
            desc.Path = path;
            desc.ID = new FileID();
            desc.ID.TTH = node.Attributes["TTH"].Value;
            desc.ID.Size = size;
            if (!double.TryParse(node.Attributes["watchedCount"].Value, out desc.WatchedCount))
                desc.WatchedCount = 0.0;

            return desc;
        }

        bool mDisposed = false;
        public void Dispose()
        {
            if (mDisposed)
                return;
            mDisposed = true;
            mThread.Abort();
            save();
        }
    }
}
